package ch.ethz.fcl.metrobuzz.data.ltt.link;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.vecmath.Vector2d;

import org.lwjgl.opengl.GL11;

import ch.ethz.fcl.metrobuzz.algorithm.list.ListOperation;
import ch.ethz.fcl.metrobuzz.data.ltt.TrajectoryGraph;
import ch.ethz.fcl.metrobuzz.data.ltt.node.LTTNode;
import ch.ethz.fcl.metrobuzz.render.techniques.colors.MBColorTable;
import ch.ethz.fcl.metrobuzz.render.techniques.projection.Projection;
import ch.ethz.fcl.metrobuzz.spatialindex.object2d.Point2D;
import ch.ethz.fcl.metrobuzz.tools.MBGL;
import ch.ethz.fcl.metrobuzz.tools.MBTools;

public class LTTLink {
	private TrajectoryGraph trajGraph = null;

	private LTTNode from;
	private LTTNode to;
	private List<Integer> passTrajIndices = null;

	private List<LTTLink> preLinks = null;
	private List<LTTLink> postLinks = null;

	// records the volume of flows starting from each of the node passing by
	// this link
	private Map<LTTNode, Integer> nodeFlowVolumeMap = null;

	// corresponding long link
	private LTTLongLink longLink = null;

	public LTTLink(TrajectoryGraph trajGraph, LTTNode from, LTTNode to) {
		this.trajGraph = trajGraph;
		this.from = from;
		this.to = to;
	}

	public void addPassTrajIndex(int index) {
		if (passTrajIndices == null)
			passTrajIndices = new ArrayList<Integer>();

		if (!passTrajIndices.contains(index))
			passTrajIndices.add(index);
	}

	public void addPreLink(LTTLink link) {
		if (preLinks == null)
			preLinks = new ArrayList<LTTLink>();

		if (!preLinks.contains(link))
			preLinks.add(link);
	}

	public void addPostLink(LTTLink link) {
		if (postLinks == null)
			postLinks = new ArrayList<LTTLink>();

		if (!postLinks.contains(link))
			postLinks.add(link);
	}

	public void setLongLink(LTTLongLink longlink) {
		this.longLink = longlink;
	}

	public void createNodeFlowVolumeMap(List<LTTLink> visitedLinks) {
		if (visitedLinks == null)
			visitedLinks = new ArrayList<LTTLink>();

		if (visitedLinks.contains(this))
			return;

		visitedLinks.add(this);
		if (preLinks != null) {
			List<LTTNode> comingNodes = new ArrayList<LTTNode>();
			for (LTTLink link : preLinks) {
				link.createNodeFlowVolumeMap(visitedLinks);
				for (LTTNode node : link.nodeFlowVolumeMap.keySet())
					if (!comingNodes.contains(node))
						comingNodes.add(node);
			}

			nodeFlowVolumeMap = new LinkedHashMap<LTTNode, Integer>();
			List<Integer> otherNodeTrajs = new ArrayList<Integer>();
			for (LTTNode node : comingNodes) {
				List<Integer> nodeComingTrajs = ListOperation.intersection(
						node.getStartTrajIndices(), passTrajIndices);
				otherNodeTrajs.addAll(nodeComingTrajs);
				if (nodeComingTrajs.size() != 0)
					nodeFlowVolumeMap.put(node, nodeComingTrajs.size());
			}

			List<Integer> fromNodeTrajs = ListOperation.difference(
					passTrajIndices, otherNodeTrajs);
			from.addStartTrajIndices(fromNodeTrajs);
			if (fromNodeTrajs.size() != 0)
				nodeFlowVolumeMap.put(from, fromNodeTrajs.size());
		} else {
			nodeFlowVolumeMap = new LinkedHashMap<LTTNode, Integer>();
			this.from.addStartTrajIndices(passTrajIndices);
			nodeFlowVolumeMap.put(from, passTrajIndices.size());
		}

		// if (trajGraph.DEBUG) {
		// System.out.println("Link " + toString() + " coming nodes: ");
		// for (LTTNode node : nodeFlowVolumeMap.keySet())
		// System.out.print(node.toNode().getId() + " ");
		// System.out.println();
		// }

		if (postLinks != null) {
			for (LTTLink link : postLinks)
				link.createNodeFlowVolumeMap(visitedLinks);
		}
	}

	public void debugConnectedLinks(List<LTTLink> visitedLinks) {
		if (visitedLinks == null)
			visitedLinks = new ArrayList<LTTLink>();

		if (visitedLinks.contains(this))
			return;

		System.out.println("For link " + toString());
		visitedLinks.add(this);
		if (preLinks != null) {
			System.out.println(" has " + preLinks.size() + " pre-links");
			for (int i = 0; i < preLinks.size(); i++)
				preLinks.get(i).debugConnectedLinks(visitedLinks);
		}

		if (postLinks != null) {
			System.out.println(" has " + postLinks.size() + " post-links");
			for (int i = 0; i < postLinks.size(); i++)
				postLinks.get(i).debugConnectedLinks(visitedLinks);
		}
	}

	public void render() {
		if (nodeFlowVolumeMap == null)
			return;

		Projection proj = trajGraph.getData().getContainer().getViewer()
				.getProjection();
		float onePixel = proj.getWorldDistance(1);

		if (longLink == null) {
			renderAsArrow();
		} else {
			int linkIndex = longLink.getLinks().indexOf(this);
			Point2D[] spline = longLink.getSpline(this);
			if (spline == null)
				renderAsArrow();
			else {
				LTTNode selectedNode = trajGraph.getSelectedLTTNodeMap().get(
						trajGraph.getSelectedNodeId());
				float totalVolume = selectedNode.getInterchange().getCircos()
						.getData().getTotalSum();
				float oneLength = (float) (Math.PI
						* selectedNode.getInterchange().getCircosRadius() / totalVolume);
				// float linkVolumeLength = oneLength * passTrajIndices.size();

				Point2D[][] itemLinkPts = new Point2D[nodeFlowVolumeMap.size() + 1][spline.length];

				for (int i = 0; i < spline.length; i++) {
					Point2D p0 = spline[i];
					Point2D p1 = null;
					Vector2d dir = null;
					if (i != spline.length - 1) {
						p1 = spline[i + 1];
						dir = p0.getDir(p1);
					} else {
						p1 = spline[i - 1];
						dir = p1.getDir(p0);
					}

					for (int j = 0; j < nodeFlowVolumeMap.size(); j++) {
						LTTNode node = (LTTNode) nodeFlowVolumeMap.keySet()
								.toArray()[j];

						float nodeVolumeLength = nodeFlowVolumeMap.get(node)
								* oneLength;
						if (node.equals(from))
							nodeVolumeLength = 0;

						float nodeVolumeLengthInNextLink = nodeVolumeLength;
						if (linkIndex != longLink.getLinks().size() - 1) {
							LTTLink nextLink = longLink.getLinks().get(
									linkIndex + 1);
							nodeVolumeLengthInNextLink = nextLink
									.getNodeVolume(node) * oneLength;
						} else
							nodeVolumeLengthInNextLink = nodeFlowVolumeMap
									.get(node) * oneLength;

						if (postLinks == null)
							nodeVolumeLengthInNextLink = 0;

						float length = (nodeVolumeLengthInNextLink - nodeVolumeLength)
								/ (spline.length - 1) * i + nodeVolumeLength;

						Vector2d verDir = MBTools.rotate(dir, Math.PI / 2);

						if (j == 0) {
							// itemLinkPts[0][i] = p0.getPointAt(inverseVerDir,
							// volumeLength / 2);
							itemLinkPts[0][i] = p0
									.getPointAt(dir, onePixel * 5);
							itemLinkPts[1][i] = itemLinkPts[0][i].getPointAt(
									verDir, length);
						} else
							itemLinkPts[j + 1][i] = itemLinkPts[j][i]
									.getPointAt(verDir, length);
					}
				}
				for (int k = 0; k < nodeFlowVolumeMap.size(); k++) {
					LTTNode node = (LTTNode) nodeFlowVolumeMap.keySet()
							.toArray()[k];
					int colorIndex = trajGraph.getLTTNodeIndex(node);
					MBColorTable.assignColor(colorIndex, 1.0f);
					GL11.glBegin(GL11.GL_TRIANGLE_STRIP);
					for (int i = 0; i < spline.length; i++) {
						MBGL.render(itemLinkPts[k][i]);
						MBGL.render(itemLinkPts[k + 1][i]);
					}
					GL11.glEnd();
				}
			}
		}

	}

	private void renderAsArrow() {
		LTTNode selectedNode = trajGraph.getSelectedLTTNodeMap().get(
				trajGraph.getSelectedNodeId());
		float totalVolume = selectedNode.getInterchange().getCircos().getData()
				.getTotalSum();
		float oneLength = 2 * selectedNode.getInterchange().getCircosRadius()
				/ totalVolume;
		float volumeLength = oneLength * passTrajIndices.size();

		Point2D start = from.toNode().toPoint();
		Point2D end = to.toNode().toPoint();

		Vector2d dir = start.getDir(end);
		Vector2d inverseVerticalDir = MBTools.rotate(dir, -Math.PI / 2);
		Vector2d verticalDir = MBTools.rotate(dir, Math.PI / 2);
		Point2D nodeFlowLowPointStart = start.getPointAt(inverseVerticalDir,
				volumeLength / 2);
		Point2D nodeFlowLowPointEnd = end.getPointAt(inverseVerticalDir,
				volumeLength / 2);

		for (LTTNode node : nodeFlowVolumeMap.keySet()) {
			float nodeVolumeLength = nodeFlowVolumeMap.get(node) * oneLength;
			Point2D nodeFlowMidPointStart = nodeFlowLowPointStart.getPointAt(
					verticalDir, nodeVolumeLength / 2);
			Point2D nodeFlowMidPointEnd = nodeFlowLowPointEnd.getPointAt(
					verticalDir, nodeVolumeLength / 2);

			int colorIndex = trajGraph.getLTTNodeIndex(node);
			MBColorTable.assignColor(colorIndex, 1.0f);
			MBGL.renderWithMid(nodeFlowMidPointStart, nodeFlowMidPointEnd,
					nodeVolumeLength);

			nodeFlowLowPointStart = nodeFlowLowPointStart.getPointAt(
					verticalDir, nodeVolumeLength);
			nodeFlowLowPointEnd = nodeFlowLowPointEnd.getPointAt(verticalDir,
					nodeVolumeLength);
		}
	}

	public LTTNode getFrom() {
		return from;
	}

	public LTTNode getTo() {
		return to;
	}

	public List<LTTLink> getPreLinks() {
		return preLinks;
	}

	public List<LTTLink> getPostLinks() {
		return postLinks;
	}

	public int getNodeVolume(LTTNode startNode) {
		Integer volume = nodeFlowVolumeMap.get(startNode);
		if (volume == null)
			volume = 0;
		return volume;
	}

	@Override
	public String toString() {
		return from.toNode().getId()
				+ "->"
				+ to.toNode().getId()
				+ (passTrajIndices == null ? ""
						: (" traj size " + passTrajIndices.size()));
	}

	@Override
	public boolean equals(Object o) {
		if (!(o instanceof LTTLink))
			return false;
		LTTLink link = (LTTLink) o;
		if (!link.getFrom().equals(from))
			return false;
		if (!link.getTo().equals(to))
			return false;
		return true;
	}
}
